package nhooyr

import (
	"bytes"
	handler_common "clean/adapter/io/common"
	"clean/infra/config"
	"clean/infra/io"
	"clean/infra/io/driver"
	"clean/usecase"
	"clean/usecase/io/pb"
	"context"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"
	"log"
	"net/http"
	"nhooyr.io/websocket"
	"strconv"
	"time"
)


type ioDriver struct {
}

func init() {
	io.Register("websocket_nhooyr", &ioDriver{})
}

func (ioDriver) Create(cfg config.Config, app usecase.Input) (io_driver.Io, error) {
	return NewServerNhooyr(cfg, app)
}

// https://github.com/nhooyr/websocket
type ServerNhooyr struct {
	router *io_driver.CommonRouter
	server *http.Server
}

func NewServerNhooyr(cfg config.Config, app usecase.Input) (*ServerNhooyr, error) {
	s := &ServerNhooyr{
		router: &io_driver.CommonRouter{},
		server: &http.Server{
			Addr: ":" + strconv.Itoa(8080),
		},
	}
	s.server.Handler = s
	h := handler_common.NewHandler(app)
	h.Init(s.router)
	return s, nil
}

func (s *ServerNhooyr) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	c, err := websocket.Accept(w, r, nil)
	if err != nil {
		return
	}
	defer func() {
		err := c.Close(websocket.StatusInternalError, "unknown")
		if err != nil {
			// todo
		}
	}()
	for {
		ctx, cancel := context.WithTimeout(r.Context(), time.Second*3)
		defer cancel()
		msgType, reader, err := c.Reader(ctx)
		if err != nil {
			log.Printf("reader err: %v\n", err)
			break
		}
		b := &bytes.Buffer{}
		_, err = b.ReadFrom(reader)
		if err != nil {
			log.Printf("read err: %v\n", err)
			continue
		}
		in := &pb.Input{}
		if msgType == websocket.MessageText {
			err = protojson.Unmarshal(b.Bytes(), in)
		} else if msgType == websocket.MessageBinary {
			err = proto.Unmarshal(b.Bytes(), in)
		}
		if err != nil {
			log.Printf("proto req err: %v\n", err)
			continue
		}
		ir := in.ProtoReflect()
		wd := ir.WhichOneof(ir.Descriptor().Oneofs().Get(0))
		if wd == nil {
			// todo req error
			continue
		}
		handler := s.router.Get(string(wd.Name()))
		if handler == nil {
			// todo
			continue
		}
		out := &pb.Output{}
		handler(*in, out)
		var bs []byte
		if msgType == websocket.MessageText {
			bs, _ = protojson.Marshal(out)
		} else if msgType == websocket.MessageBinary {
			bs, _ = proto.Marshal(out)
		}
		c.Write(context.Background(), msgType, bs)
	}
}

func (s *ServerNhooyr) Start() error {
	return s.server.ListenAndServe()
}
